﻿using NFox.Runtime.Com;
using System;
using System.Collections;
using System.Dynamic;
using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;

namespace NFox.Runtime
{
    /// <summary>
    /// Com对象封装类
    /// </summary>
    public class Comproxy : DynamicObject, IEnumerable
    {
        public IDispatch Object { get; private set; }

        public Com.Reflection.Type RealType { get; private set; }

        private Comproxy(object obj)
        {
            Object = (IDispatch)obj;
            RealType = Com.Reflection.Assembly.GetType(Object);
        }

        public override string ToString()
        {
            return $"Comproxy, {RealType}";
        }

        #region Wrap

        /// <summary>
        /// 封装Com对象
        /// </summary>
        /// <param name="obj">Com对象</param>
        /// <returns></returns>
        public static dynamic Wrap(object obj)
        {
            if (obj == null)
            {
                return null;
            }
            else if (Marshal.IsComObject(obj))
            {
                return new Comproxy(obj);
            }
            else if (obj is object[])
            {
                var arr = obj as object[];
                for (int i = 0; i < arr.Length; i++)
                    arr[i] = Wrap(arr[i]);
            }

            return obj;
        }

        /// <summary>
        /// 获取指定对象的运行实例
        /// </summary>
        /// <param name="progId"></param>
        /// <returns></returns>
        public static dynamic GetObject(string progId)
        {
            return new Comproxy(Marshal.GetActiveObject(progId));
        }

        /// <summary>
        /// 创建指定对象的运行实例
        /// </summary>
        /// <param name="progId"></param>
        /// <returns></returns>
        public static dynamic CreateObject(string progId)
        {
            return
                new Comproxy(
                    Activator.CreateInstance(
                        Type.GetTypeFromProgID(
                            progId)));
        }

        /// <summary>
        /// 创建指定对象的运行实例
        /// </summary>
        /// <param name="clsId"></param>
        /// <returns></returns>
        public static dynamic CreateObject(Guid clsId)
        {
            return
                new Comproxy(
                    Activator.CreateInstance(
                        Type.GetTypeFromCLSID(
                            clsId)));
        }

        /// <summary>
        /// 创建Com对象数组
        /// </summary>
        /// <param name="objs"></param>
        /// <returns></returns>
        public static dynamic CreateArray(params Comproxy[] objs)
        {
            var arr = new DispatchWrapper[objs.Length];
            for (int i = 0; i < objs.Length; i++)
                arr[i] = new DispatchWrapper(objs[i].Object);
            return arr;
        }

        #endregion Wrap

        #region IEnumerable

        private int _getCount()
        {
            var pi = RealType.Properties["Count"];
            var pars =
                new ComTypes.DISPPARAMS
                {
                    cArgs = 0,
                    cNamedArgs = 0
                };
            Variant res;
            int err;
            Object.Invoke(
                pi.Index, Guid.Empty, 0,
                ComTypes.INVOKEKIND.INVOKE_PROPERTYGET,
                pars, out res, IntPtr.Zero, out err);
            return res.IntegerValue;
        }

        private object _getItem(object index)
        {
            var mi = RealType.Functions["Item"];
            using (var iptr = new Utils.IPTrans())
            {
                var ipArgs = iptr.Alloc<Variant>();
                Marshal.GetNativeVariantForObject(index, ipArgs);
                var pars =
                    new ComTypes.DISPPARAMS
                    {
                        cNamedArgs = 0,
                        cArgs = 1,
                        rgvarg = ipArgs,
                    };
                Variant res;
                int err;
                Object.Invoke(
                    mi.Index, Guid.Empty, 0,
                    ComTypes.INVOKEKIND.INVOKE_FUNC,
                    pars, out res, IntPtr.Zero, out err);
                return Wrap(res.ToObject());
            }
        }

        public object this[int index]
        {
            get { return _getItem(index); }
        }

        public object this[string name]
        {
            get { return _getItem(name); }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            if (RealType.IsCollection)
            {
                var count = _getCount();
                for (int i = 0; i < count; i++)
                    yield return _getItem(i);
            }
        }

        #endregion IEnumerable

        #region Dynamic

        /// <summary>
        /// 属性获取
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (RealType.Properties.ContainsKey(binder.Name))
            {
                var pi = RealType.Properties[binder.Name];
                if (pi.CanGet)
                {
                    var pars =
                        new ComTypes.DISPPARAMS
                        {
                            cArgs = 0,
                            cNamedArgs = 0
                        };
                    Variant res;
                    int err;
                    Object.Invoke(
                        pi.Index, Guid.Empty, 0,
                        ComTypes.INVOKEKIND.INVOKE_PROPERTYGET,
                        pars, out res, IntPtr.Zero, out err);
                    if (err == 0)
                    {
                        result = Wrap(res.ToObject());
                        return true;
                    }
                }
            }

            result = null;
            return false;
        }

        /// <summary>
        /// 属性设置
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (RealType.Properties.ContainsKey(binder.Name))
            {
                var pi = RealType.Properties[binder.Name];
                if (pi.CanSet)
                {
                    using (var iptr = new Utils.IPTrans())
                    {
                        var ipNamedArgs = iptr.Alloc<int>();
                        Marshal.StructureToPtr(-3, ipNamedArgs, false);
                        var ipArgs = iptr.Alloc<Variant>();
                        Marshal.GetNativeVariantForObject(value, ipArgs);

                        var pars =
                            new ComTypes.DISPPARAMS
                            {
                                cNamedArgs = 1,
                                rgdispidNamedArgs = ipNamedArgs,
                                cArgs = 1,
                                rgvarg = ipArgs,
                            };
                        Variant res;
                        int err;
                        Object.Invoke(
                            pi.Index, Guid.Empty, 0,
                            pi.SetKind,
                            pars, out res, IntPtr.Zero, out err);
                        return err == 0;
                    }
                }
            }

            return false;
        }

        //0x80020004;
        private static readonly ErrorWrapper _missing = new ErrorWrapper(-2147352572);

        /// <summary>
        /// 函数调用
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            if (RealType.Functions.ContainsKey(binder.Name))
            {
                var fi = RealType.Functions[binder.Name];
                var num = fi.Params.Count;
                if (args.Length > num)
                    throw new Exception("参数过多！");

                using (var iptr = new Utils.IPTrans())
                {
                    int size = Marshal.SizeOf(typeof(Variant));
                    var ip = iptr.Alloc(num * size);
                    var ipArgs = ip;

                    //处理默认参数
                    int i = num - 1;
                    for (; i > args.Length - 1; i--)
                    {
                        Marshal.GetNativeVariantForObject(_missing, ip);
                        ip += size;
                    }

                    //参数处理,由调用者保证参数类型
                    for (; i > -1; i--)
                    {
                        var e = fi.Params[i].Element;
                        if (e.Flags["Out"])
                        {
                            //设置出参的变体变量为引用类型
                            var rip = iptr.Alloc(size);
                            if (e.Flags["In"])
                            {
                                Marshal.GetNativeVariantForObject(args[i], rip);
                            }
                            else
                            {
                                var value = new Variant();
                                Marshal.StructureToPtr(value, rip, false);
                            }
                            var rvalue = new Variant();
                            rvalue.VariantType = fi.Params[i].Element.VariantType | VarEnum.VT_BYREF;
                            rvalue.RefValue = rip;
                            Marshal.StructureToPtr(rvalue, ip, false);
                        }
                        else
                        {
                            Marshal.GetNativeVariantForObject(args[i], ip);
                        }

                        ip += size;
                    }

                    var pars =
                        new ComTypes.DISPPARAMS
                        {
                            cNamedArgs = 0,
                            cArgs = num,
                            rgvarg = ipArgs,
                        };

                    Variant res;
                    int err;
                    Object.Invoke(
                        fi.Index, Guid.Empty, 0,
                        ComTypes.INVOKEKIND.INVOKE_FUNC,
                        pars, out res, IntPtr.Zero, out err);

                    if (err == 0)
                    {
                        result =
                            fi.Element.VariantType == VarEnum.VT_VOID ?
                            null : Wrap(res.ToObject());

                        i = num - 1;
                        for (; i > args.Length - 1; i--)
                            ipArgs += size;

                        //设置出参
                        for (; i > -1; i--)
                        {
                            if (fi.Params[i].Element.Flags["Out"])
                                args[i] = Wrap(Marshal.GetObjectForNativeVariant(ipArgs));
                            ipArgs += size;
                        }

                        return true;
                    }
                    else
                    {
                        throw new Exception($"函数{binder.Name}调用错误！");
                    }
                }
            }

            result = null;
            return false;
        }

        #endregion Dynamic
    }
}